iT邦幫忙

2026 iThome 鐵人賽

0
AI Engineering

Java × AI:30天打造智慧健康助理系列 第 17 篇

用 Java 分析七天飲水量

  • 分享至 

  • xImage
  •  

Day 17|用 Java 分析七天飲水量

昨天的 Day 16,我們成功建立了飲水量歷史查詢 API。

現在不只可以查詢今天的飲水量,也可以查看指定日期及最近七天的紀錄。

例如:

[
  {
    "date": "2026-10-15",
    "totalWater": 1800
  },
  {
    "date": "2026-10-16",
    "totalWater": 1400
  },
  {
    "date": "2026-10-17",
    "totalWater": 1600
  }
]

雖然已經可以查詢歷史資料,但如果只是回傳一大串 JSON,好像還是不太容易看出整體情況。

所以今天我想再進一步:

既然已經有歷史紀錄了,為什麼不直接讓 Java 幫我們計算統計結果?

例如:

  • 最近七天平均記錄了多少飲水量?
  • 哪一天的紀錄量最高?
  • 哪一天的紀錄量最低?
  • 七天總共記錄了多少飲水量?

這些問題其實不需要 AI,使用 Java 就可以完成。

今天的目標是:

使用 Java 處理資料庫查詢結果,建立七天飲水量統計 API。


一、今天要完成什麼功能?

目前已經有以下 API:

HTTP Method API 功能
POST /health/water 新增飲水紀錄
GET /health/water 查詢今日飲水量
GET /health/water/date 查詢指定日期
GET /health/water/history 查詢最近七天

今天準備新增:

GET /health/water/statistics

讓使用者可以直接取得最近七天的統計結果。

例如:

{
  "totalWater": 10500,
  "averageWater": 1500.0,
  "maxWater": 2000,
  "maxDate": "2026-10-15",
  "minWater": 1000,
  "minDate": "2026-10-12",
  "unit": "ml"
}

這樣就不用自己拿計算機慢慢加總了 XD


二、先想清楚統計的範圍

在開始寫程式之前,我覺得有一件事情需要先決定。

七天平均飲水量,到底要怎麼計算?

假設最近七天只有五天有紀錄:

日期 飲水紀錄
10/11 1200 ml
10/12 無紀錄
10/13 1500 ml
10/14 1800 ml
10/15 無紀錄
10/16 1400 ml
10/17 1600 ml

如果只計算有紀錄的五天,平均值會是:

7500 ÷ 5 = 1500 ml

但如果計算完整七天,並把沒有紀錄的日期暫時以 0 表示:

7500 ÷ 7 ≈ 1071.43 ml

兩個結果其實差很多。

所以今天決定採用:

最近七個日曆日的平均紀錄量。

也就是:

七天總紀錄量 ÷ 7

不過要特別注意:

沒有紀錄不代表當天真的沒有喝水。

今天的統計只是根據系統保存的資料進行計算,不能直接代表使用者實際的飲水情況。

為了避免誤解,我們也會在結果中顯示有紀錄的天數。


三、先回顧 WaterHistoryResponse

昨天我們已經建立:

WaterHistoryResponse.java

用來儲存每一天的飲水總量。

程式如下:

package com.example.healthassistant.dto;

import java.time.LocalDate;

public class WaterHistoryResponse {

    private LocalDate date;
    private int totalWater;

    public WaterHistoryResponse(LocalDate date, int totalWater) {
        this.date = date;
        this.totalWater = totalWater;
    }

    public LocalDate getDate() {
        return date;
    }

    public int getTotalWater() {
        return totalWater;
    }
}

例如資料庫查詢結果是:

10/15 → 1800 ml
10/16 → 1400 ml
10/17 → 1600 ml

Java 就可以把它們整理成:

List<WaterHistoryResponse>

今天會直接使用這份歷史資料進行統計。

因此不需要重新建立資料表,也不需要修改昨天的 SQL。


四、建立 WaterStatisticsResponse DTO

接下來建立今天的新檔案:

WaterStatisticsResponse.java

因為這次要回傳的資料比較多,所以我決定建立一個專門的 DTO。

完整程式如下:

package com.example.healthassistant.dto;

import java.time.LocalDate;

public class WaterStatisticsResponse {

    private long totalWater;
    private double averageWater;
    private int maxWater;
    private LocalDate maxDate;
    private int minWater;
    private LocalDate minDate;
    private int recordedDays;
    private int periodDays;
    private String unit;

    public WaterStatisticsResponse(
            long totalWater,
            double averageWater,
            int maxWater,
            LocalDate maxDate,
            int minWater,
            LocalDate minDate,
            int recordedDays,
            int periodDays) {

        this.totalWater = totalWater;
        this.averageWater = averageWater;
        this.maxWater = maxWater;
        this.maxDate = maxDate;
        this.minWater = minWater;
        this.minDate = minDate;
        this.recordedDays = recordedDays;
        this.periodDays = periodDays;
        this.unit = "ml";
    }

    public long getTotalWater() {
        return totalWater;
    }

    public double getAverageWater() {
        return averageWater;
    }

    public int getMaxWater() {
        return maxWater;
    }

    public LocalDate getMaxDate() {
        return maxDate;
    }

    public int getMinWater() {
        return minWater;
    }

    public LocalDate getMinDate() {
        return minDate;
    }

    public int getRecordedDays() {
        return recordedDays;
    }

    public int getPeriodDays() {
        return periodDays;
    }

    public String getUnit() {
        return unit;
    }
}

這次的 DTO 包含:

欄位 說明
totalWater 七天累積紀錄量
averageWater 七天平均紀錄量
maxWater 單日最高紀錄量
maxDate 最高紀錄日期
minWater 單日最低紀錄量
minDate 最低紀錄日期
recordedDays 有紀錄的天數
periodDays 統計期間天數
unit 單位

因為這些資料都有固定的欄位,所以使用 DTO 會比一直使用 Map 更容易管理。


五、先計算七天飲水總量

接下來開始撰寫統計邏輯。

打開:

WaterService.java

今天要新增一個 Method:

getWaterStatistics()

首先取得最近七天的歷史紀錄:

List<WaterHistoryResponse> history = getWaterHistory();

接著使用 for 迴圈計算總量:

long total = 0;

for (WaterHistoryResponse record : history) {
    total += record.getTotalWater();
}

假設歷史資料是:

1200
1500
1800
1400
1600

就會得到:

total = 7500

這樣就完成第一個統計功能。

這裡使用 long 儲存總量,是為了避免多筆整數資料加總時太容易超出 int 的範圍。


六、計算七天平均飲水量

接著要計算平均值。

公式很簡單:

平均飲水紀錄量 = 七天總紀錄量 ÷ 7

在 Java 裡面可以寫:

double average = total / 7.0;

這裡使用:

7.0

而不是:

7

因為我們希望保留小數。

例如:

7500 ÷ 7 = 1071.428571...

如果希望只顯示小數點後兩位,可以使用:

double roundedAverage =
        Math.round(average * 100.0) / 100.0;

結果就會是:

1071.43

這樣就完成平均值計算。


七、找出最高與最低紀錄

接下來要找出:

最高飲水紀錄
最低飲水紀錄

我們可以使用 Java 的 if 條件判斷。

不過這次要稍微注意一件事情:

如果最近七天完全沒有紀錄,最高和最低值應該怎麼處理?

如果直接把最高和最低都設成 0,可能會讓使用者以為系統真的有一筆 0 ml 的紀錄。

所以今天決定:

  • 如果有紀錄,就找出最高和最低紀錄。
  • 如果完全沒有紀錄,最高與最低日期回傳 null。
  • 最高與最低數值則先回傳 0。

這樣可以避免在完全沒有資料時,憑空產生某一天的最高或最低紀錄。


八、建立完整的統計邏輯

接下來把剛剛的功能整理成一個 Method。

在 WaterService.java 加入:

public WaterStatisticsResponse getWaterStatistics() {

    List<WaterHistoryResponse> history = getWaterHistory();

    long total = 0;

    int maxWater = 0;
    int minWater = 0;

    LocalDate maxDate = null;
    LocalDate minDate = null;

    boolean firstRecord = true;

    for (WaterHistoryResponse record : history) {

        int amount = record.getTotalWater();

        total += amount;

        if (firstRecord) {

            maxWater = amount;
            minWater = amount;

            maxDate = record.getDate();
            minDate = record.getDate();

            firstRecord = false;

        } else {

            if (amount > maxWater) {
                maxWater = amount;
                maxDate = record.getDate();
            }

            if (amount < minWater) {
                minWater = amount;
                minDate = record.getDate();
            }
        }
    }

    double average = total / 7.0;

    double roundedAverage =
            Math.round(average * 100.0) / 100.0;

    return new WaterStatisticsResponse(
        total,
        roundedAverage,
        maxWater,
        maxDate,
        minWater,
        minDate,
        history.size(),
        7
    );
}

這段程式雖然比較長,但其實就是把今天的統計功能全部放在一起。

主要流程是:

取得最近七天紀錄
        ↓
建立統計變數
        ↓
使用 for 迴圈讀取資料
        ↓
計算總量
        ↓
找出最高紀錄
        ↓
找出最低紀錄
        ↓
計算七天平均
        ↓
回傳統計 DTO

另外,因為昨天的 getWaterHistory() 只會回傳有紀錄的日期,所以這裡的最高和最低,是指:

有紀錄日期中的最高與最低飲水量。

不是完整七天中實際飲水量的最高或最低。


九、修改 WaterService

今天不用把整個 WaterService 重新寫一次。

只需要保留之前的功能,再加入:

getWaterStatistics()

就可以了。

記得在檔案上方加入:

import com.example.healthassistant.dto.WaterStatisticsResponse;

目前 WaterService 的主要 Method 就會有:

addWater()

新增飲水紀錄。

getTotalWater()

查詢今天的飲水量。

getTotalWaterByDate()

查詢指定日期。

getWaterHistory()

查詢最近七天的紀錄。

以及今天新增的:

getWaterStatistics()

計算七天統計結果。

這樣 Service 的功能就越來越完整了。


十、建立統計 API

接下來要讓使用者可以透過網址取得統計結果。

打開:

WaterController.java

加入:

@GetMapping("/statistics")
public ResponseEntity<WaterStatisticsResponse> getStatistics() {

    WaterStatisticsResponse statistics =
            waterService.getWaterStatistics();

    return ResponseEntity.ok(statistics);
}

記得加入:

import com.example.healthassistant.dto.WaterStatisticsResponse;

因為之前已經使用:

@RequestMapping("/health/water")

所以這個 Method 對應的完整路徑就是:

GET /health/water/statistics

這樣就完成今天的新 API!


十一、使用 Postman 測試

接下來重新啟動 Spring Boot。

在 Postman 輸入:

GET http://localhost:8080/health/water/statistics

不需要傳送 Request Body。

假設最近七天有以下紀錄:

日期 飲水紀錄
10/11 1200 ml
10/12 1000 ml
10/13 1500 ml
10/14 1800 ml
10/15 2000 ml
10/16 1400 ml
10/17 1600 ml

七天總量:

1200 + 1000 + 1500 + 1800 + 2000 + 1400 + 1600
= 10500 ml

七天平均:

10500 ÷ 7 = 1500 ml

最高紀錄:

2000 ml

最低紀錄:

1000 ml

因此預期回傳:

{
  "totalWater": 10500,
  "averageWater": 1500.0,
  "maxWater": 2000,
  "maxDate": "2026-10-15",
  "minWater": 1000,
  "minDate": "2026-10-12",
  "recordedDays": 7,
  "periodDays": 7,
  "unit": "ml"
}

這樣就成功把原本一筆一筆的飲水紀錄,整理成比較容易理解的統計資訊。


十二、如果最近七天沒有紀錄呢?

接下來考慮一個特殊情況。

如果資料庫裡完全沒有最近七天的飲水紀錄,系統會回傳什麼?

因為:

history.size()

會是:

0

所以總量和平均值都會是 0。

最高與最低日期則會是 null。

例如:

{
  "totalWater": 0,
  "averageWater": 0.0,
  "maxWater": 0,
  "maxDate": null,
  "minWater": 0,
  "minDate": null,
  "recordedDays": 0,
  "periodDays": 7,
  "unit": "ml"
}

這樣至少可以讓系統正常回傳資料,而不會因為沒有紀錄就發生錯誤。

不過未來如果要讓前端顯示這些資訊,可能需要再設計更清楚的提示。

例如:

最近七天尚無飲水紀錄。

而不是直接告訴使用者平均飲水量是 0。


十三、目前的專案架構

今天主要新增了一個 DTO,並擴充 Service 和 Controller。

目前專案結構如下:

health-assistant
│
├── src/main/java/com/example/healthassistant
│   │
│   ├── HealthAssistantApplication.java
│   │
│   ├── controller
│   │   ├── HealthController.java
│   │   └── WaterController.java
│   │
│   ├── service
│   │   ├── BmiService.java
│   │   └── WaterService.java
│   │
│   ├── repository
│   │   └── WaterRepository.java
│   │
│   ├── dto
│   │   ├── HealthRequest.java
│   │   ├── WaterRequest.java
│   │   ├── WaterHistoryResponse.java
│   │   └── WaterStatisticsResponse.java
│   │
│   └── exception
│       └── GlobalExceptionHandler.java
│
├── src/main/resources
│   ├── application.properties
│   └── schema.sql
│
└── pom.xml

今天的資料流程變成:

GET /health/water/statistics
              ↓
       WaterController
              ↓
        WaterService
              ↓
       WaterRepository
              ↓
         H2 Database
              ↓
      取得七天飲水紀錄
              ↓
      Java 計算統計結果
              ↓
   WaterStatisticsResponse
              ↓
          JSON 回傳

現在系統不只可以儲存資料,也開始具備基本的統計能力。


十四、為什麼今天不用 AI?

這次的功能包含:

加總
平均
最大值
最小值

這些都是有明確計算規則的事情。

所以直接使用 Java 處理就可以了。

如果讓 AI 幫忙計算,反而可能產生不必要的誤差。

例如:

Java → 負責數值計算
AI → 負責將統計結果整理成文字

這樣的分工比較適合我們目前的專案。

未來如果想產生一段摘要:

最近七天共有七天留下飲水紀錄,
平均每日紀錄量為 1500 ml,
其中 10/15 的紀錄量最高。

就可以先讓 Java 算出正確數字,再將統計結果提供給 AI 整理。

不過目前這些數字仍然只是飲水紀錄的統計,不能直接用來判斷個人的健康狀況。


十五、今天還有哪些可以改進的地方?

雖然今天已經完成統計 API,但還有一些值得改進的地方。

1. 補上沒有紀錄的日期

目前歷史查詢只會回傳有紀錄的日期。

如果未來要製作完整的七天圖表,最好能顯示每天的日期,並清楚區分:

有紀錄

和:

沒有紀錄

而不是直接把沒有紀錄當成真的喝了 0 ml。

2. 支援自訂日期範圍

目前只能查詢最近七天。

未來可以讓使用者指定:

開始日期
結束日期

例如:

GET /health/water/statistics?startDate=2026-10-01&endDate=2026-10-17

這樣就能查詢更長時間的統計資料。

3. 加入更多統計指標

除了平均值、最大值和最小值,也可以加入:

有紀錄天數
每日變化
每週總量

讓使用者更容易理解自己的紀錄。

4. 將統計邏輯獨立出來

目前我們直接把統計邏輯放在 WaterService。

但如果未來統計功能越來越多,也可以考慮建立:

WaterStatisticsService

專門處理統計分析。

這樣就能讓不同功能有更清楚的分工。


Day 17 小結

今天成功讓智慧健康助理從單純的飲水紀錄,進一步加入統計功能!

目前已經可以:

  1. 計算最近七天的飲水紀錄總量
  2. 計算七天平均紀錄量
  3. 找出有紀錄日期中的最高飲水量
  4. 找出有紀錄日期中的最低飲水量
  5. 計算有紀錄的天數
  6. 使用 DTO 回傳統計結果
  7. 建立新的統計 API

今天也再次練習了 Java 的:

for

以及:

if

條件判斷。

目前的智慧健康助理已經可以:

新增飲水紀錄
      ↓
儲存至資料庫
      ↓
查詢歷史資料
      ↓
計算七天統計
      ↓
回傳 JSON

今天最大的收穫是:

有了資料庫之後,我們不只可以保存資料,也能透過 Java 將資料整理成有意義的統計結果。

雖然還沒有真正加入 AI,但已經開始為後面的智慧健康摘要建立基礎了!


明日預告

Day 18|健康管理不只喝水!建立睡眠時間紀錄功能

目前我們已經完成 BMI 計算、飲水紀錄,以及七天飲水量統計。

不過既然要打造智慧健康助理,當然不能只有飲水量這一種健康紀錄!

所以下一篇準備加入:

每日睡眠時間紀錄功能!

預計會學習:

  • 如何建立 SleepRequest DTO
  • 如何設計睡眠紀錄資料表
  • 如何建立 SleepRepository
  • 使用 POST API 新增睡眠紀錄
  • 使用 GET API 查詢睡眠資料

讓智慧健康助理開始記錄不同種類的日常健康資訊!


上一篇
建立飲水量歷史查詢 API
下一篇
用 Java 建立睡眠時間紀錄功能
系列文
Java × AI:30天打造智慧健康助理 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言